﻿using LightCAD.MathLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace LightCAD.Three
{

    public class WebGLClipping
    {
        private WebGLProperties properties;
        private double[] globalState;
        private int numGlobalPlanes;
        private bool localClippingEnabled;
        private bool renderingShadows;
        private Plane plane;
        private Matrix3 viewNormalMatrix;


        public Uniform uniform;
        public int numPlanes = 0;
        public int numIntersection = 0;

        public WebGLClipping(WebGLProperties properties)
        {
            this.properties = properties;
            this.globalState = null;
            this.numGlobalPlanes = 0;
            this.localClippingEnabled = false;
            this.renderingShadows = false;
            this.plane = new Plane();
            this.viewNormalMatrix = new Matrix3();

            this.uniform = new Uniform() { value = null, needsUpdate = false };
            this.numPlanes = 0;
            this.numIntersection = 0;
        }
        private double[] projectPlanes(ListEx<Plane> planes, Camera camera = null, int dstOffset = 1, bool skipTransform = false)
        {
            var nPlanes = planes?.Length ?? 0;
            double[] dstArray = null;

            if (nPlanes != 0)
            {

                dstArray = (double[])uniform.value;

                if (skipTransform != true || dstArray == null)
                {

                    int flatSize = dstOffset + nPlanes * 4;
                    Matrix4 viewMatrix = camera.matrixWorldInverse;

                    viewNormalMatrix.GetNormalMatrix(viewMatrix);

                    if (dstArray == null || dstArray.Length < flatSize)
                    {

                        dstArray = new double[flatSize];

                    }

                    for (int i = 0, i4 = dstOffset; i != nPlanes; ++i, i4 += 4)
                    {

                        plane.Copy(planes[i]).ApplyMatrix4(viewMatrix, viewNormalMatrix);

                        plane.Normal.ToArray(dstArray, i4);
                        dstArray[i4 + 3] = plane.Constant;

                    }

                }

                uniform.value = dstArray;
                uniform.needsUpdate = true;

            }

            this.numPlanes = nPlanes;
            this.numIntersection = 0;

            return dstArray;

        }

        private void resetGlobalState()
        {

            if (uniform.value != globalState)
            {
                uniform.value = globalState;
                uniform.needsUpdate = numGlobalPlanes > 0;
            }

            this.numPlanes = numGlobalPlanes;
            this.numIntersection = 0;
        }

        public bool init(ListEx<Plane> planes, bool enableLocalClipping)
        {
            bool enabled =
                planes.Length != 0 ||
                enableLocalClipping ||
                // enable state of previous frame - the clipping code has to
                // run another frame in order to reset the state:
                numGlobalPlanes != 0 ||
                localClippingEnabled;

            localClippingEnabled = enableLocalClipping;

            numGlobalPlanes = planes.Length;

            return enabled;
        }

        public void beginShadows()
        {
            renderingShadows = true;
            projectPlanes(null);
        }

        public void endShadows()
        {
            renderingShadows = false;
        }
        public void setGlobalState(ListEx<Plane> planes, Camera camera)
        {
            globalState = projectPlanes(planes, camera, 0);
        }

        public void setState(Material material, Camera camera, bool useCache)
        {
            var planes = material.clippingPlanes;
            var clipIntersection = material.clipIntersection;
            var clipShadows = material.clipShadows;

            var materialProperties = properties.get(material);

            if (!localClippingEnabled || planes == null || planes.Length == 0 || renderingShadows && !clipShadows)
            {
                // there's no local clipping

                if (renderingShadows)
                {

                    // there's no global clipping

                    projectPlanes(null);

                }
                else
                {

                    resetGlobalState();

                }

            }
            else
            {

                int nGlobal = renderingShadows ? 0 : numGlobalPlanes;
                int lGlobal = nGlobal * 4;

                var dstArray = materialProperties.clippingState;

                uniform.value = dstArray; // ensure unique state

                dstArray = projectPlanes(planes, camera, lGlobal, useCache);

                for (int i = 0; i != lGlobal; ++i)
                {

                    dstArray[i] = globalState[i];

                }

                materialProperties.clippingState = dstArray;
                this.numIntersection = clipIntersection ? this.numPlanes : 0;
                this.numPlanes += nGlobal;

            }
        }
    }
}
